﻿using DXBase;
using DXBase.Structs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Input;
using Windows.System;
using Windows.UI.Core;

namespace DXBase.D3D
{
	/// <summary>
	/// This class update the camera or current model by looking at the InputController.
	/// <see cref="Update"/>
	/// </summary>
	public class CameraController : ModelBase
	{
		public CameraController()
		{
			ModelTransform = new CenteredRotationTransform();
			ScreenSize = 1000;
		}

		#region InputController

		/// <summary>
		/// The data to update camera or model will come from here
		/// </summary>
		public InputController InputController
		{
			get { return mInputController; }
			set
			{
				if (value == mInputController)
					return;
				mInputController = value;
				OnPropertyChanged();

				Reset();
			}
		}
		InputController mInputController;

		#endregion		

		/// <summary>
		/// Will remove rotation on the mode and remove trails in the InputController
		/// </summary>
		public virtual void Reset()
		{
			ModelTransform.Rotation = QuaternionF.Rotation(1, 0, 0, 0);
			Camera.Rotate(QuaternionF.Rotation(Camera.UpVector, new Vector3F(0, 1, 0)));
			ResetInput();
		}
		/// <summary>
		/// Will remove trails in the InputController
		/// </summary>
		void ResetInput()
		{
			if (InputController != null)
			{
				InputController.TrimTrails();
				InputController.WheelValue = 0;
			}
		}

		/// <summary>
		/// Used to estimate the angle of a displacement, should be of the same order as pixel size.
		/// </summary>
		public float ScreenSize { get; set; }
		/// <summary>
		/// Current camera (after transformation)
		/// </summary>
		public Camera Camera { get; set; }
		/// <summary>
		/// Transform to apply to model
		/// </summary>
		public CenteredRotationTransform ModelTransform { get; private set; }

		/// <summary>
		/// This will update the camera and model transform from the input manager
		/// </summary>
		public void Update(PerformanceTimer timer)
		{
			if (InputController == null)
				return;

			var trail = (
				from t in InputController.GetTrails()
				where t.Points[0].CurrentPoint.PointerDevice.PointerDeviceType == PointerDeviceType.Mouse
					&& t.Pressed
				select t
			).FirstOrDefault();
			// if no mouse trail, try another device!
			if (trail == null)
				trail = (
				from t in InputController.GetTrails()
				where t.Pressed
				select t
			).FirstOrDefault();

			var keys = InputController.GetKeyStates(VirtualKey.LeftControl, VirtualKey.Home);
			bool isCam = keys[VirtualKey.LeftControl] == CoreVirtualKeyStates.None;

			var drot = QuaternionF.Rotation(1, 0, 0, 0);
			if (trail != null && trail.Points.Count > 1)
			{
				for (int i = 0; i < trail.Points.Count - 1; i++)
				{
					var P0 = trail.Points[i].CurrentPoint.Position;
					var P1 = trail.Points[i + 1].CurrentPoint.Position;

					var p0 = new Vector3F((float)P0.X, -(float)P0.Y, 0);
					var p1 = new Vector3F((float)P1.X, -(float)P1.Y, 0);
					var o = new Vector3F((float)P0.X, -(float)P0.Y, -ScreenSize);

					var v0 = p0 - o;
					var v1 = p1 - o;
					// if the world is dragged left by the mouse, the camera goes right! 
					// <=> i.e. invert rotation! (v1 to v0 as opposed to v0 to v1)
					var dr = QuaternionF.Rotation(v1, v0);

					drot = dr * drot;
				}
			}

			if (InputController.WheelValue != 0)
			{
				float dw = InputController.WheelValue * (1.0f / 60.0f);
				Camera.EyeLocation += Camera.LookVector.Normalize() * dw;
			}

			if (isCam)
			{
				Camera.Rotate(drot);
			}
			else
			{
				drot = drot.Invert();
				ModelTransform.Rotation = drot * ModelTransform.Rotation;
			}

			if (keys[VirtualKey.Home] != CoreVirtualKeyStates.None)
				Reset();
			ResetInput();
		}
	}
}
